#include <QtTest>

#include "test_SequenceRunner.h"
#include "sequencerunner.h"
#include "sequenceitem.h"
#include "SequenceModel.h"
#include "mocktimerservice.h"


test_SequenceRunner::test_SequenceRunner()
{
   qRegisterMetaType<SequenceItem::eventType>("SequenceItem::eventType");
}

/**
 * @brief executed before each test case
 */
void test_SequenceRunner::init()
{
   m_list.clear();
   m_model = new SequenceModel( m_list, this);
   m_timerService = new MockTimerService();
   m_runner = new SequenceRunner( *m_model, m_timerService, this);
}

/**
 * @brief executed at the end of every test case
 */
void test_SequenceRunner::cleanup()
{
   delete m_timerService;
   delete m_runner;
}

void test_SequenceRunner::testCreation()
{
   QVERIFY( m_runner != NULL);
}

/**
 * on sequence play command, active element is triggered immediately;
 * no matter its 'delay time'.
 */
void test_SequenceRunner::testPlay()
{
   QSignalSpy spyActivate( m_runner, SIGNAL(activateMedia(QString,bool)) );

   SequenceItem item( 1000, QString("1. Track 01"), SequenceItem::TRACK,
                      SequenceItem::NEXT_IS_UNDEFINED, this );
   m_model->addSequenceItem( &item);

   m_runner->selectIndex(0);
   m_runner->play();

   QCOMPARE( spyActivate.length(), 1);
   QCOMPARE( spyActivate.at(0).at(0).toString(), QString("1. Track 01"));
   QCOMPARE( spyActivate.at(0).at(0).toBool(), true);

   /* no timeout has been activated */
   QCOMPARE( m_timerService->getLastRequestedTime(), -1);
}

/**
 * @brief a real animation with two items
 */
void test_SequenceRunner::testAnimation()
{
   QSignalSpy spyActivate( m_runner, SIGNAL(activateMedia(QString,bool)) );

   SequenceItem item1( -1, QString("1. Track 01"), SequenceItem::TRACK);
   m_model->addSequenceItem( &item1);
   SequenceItem item2( 500, QString("2. Track 02"), SequenceItem::TRACK);
   m_model->addSequenceItem( &item2);
   SequenceItem item3( 800, QString("3. Track 03"), SequenceItem::TRACK);
   m_model->addSequenceItem( &item3);

   m_runner->selectIndex(0);
   m_runner->play();
   QVERIFY( m_runner->isPlaying());

   QCOMPARE( spyActivate.length(), 1);
   /* verify that the delay for second event has started */
   QCOMPARE(m_timerService->getLastRequestedTime(), 500);

   m_timerService->simulateTimeout();
   QCOMPARE( spyActivate.length(), 2);
   QCOMPARE( spyActivate.at(1).at(0).toString(), QString("2. Track 02"));
   QCOMPARE(m_timerService->getLastRequestedTime(), 800);

   m_timerService->simulateTimeout();
   QCOMPARE( spyActivate.length(), 3);
   QCOMPARE( spyActivate.at(2).at(0).toString(), QString("3. Track 03"));
}

void test_SequenceRunner::testDelayManual()
{
   SequenceItem item1( -1, QString("1. Track 01"), SequenceItem::TRACK);
   m_model->addSequenceItem( &item1);
   SequenceItem item2( -1, QString("2. Track 02"), SequenceItem::TRACK);
   m_model->addSequenceItem( &item2);

   m_runner->play();
   QCOMPARE( m_timerService->getLastRequestedTime(), -1);
}

/**
 * @brief when sequence is stopped, timeout for next
 *        event is not expected to arrive
 */
void test_SequenceRunner::testPause()
{
   QSignalSpy spyActivate( m_runner, SIGNAL(activateMedia(QString,bool)) );

   SequenceItem item1( -1, QString("1. Track 01"), SequenceItem::TRACK );
   m_list << &item1;

   SequenceItem item2( 500, QString("2. Track 02"), SequenceItem::TRACK );
   m_list << &item2;

   m_runner->selectIndex(0);
   m_runner->play();

   QCOMPARE( spyActivate.length(), 1);
   QCOMPARE( m_timerService->getLastRequestedTime(), 500);

   m_runner->pause();
   QVERIFY( ! m_runner->isPlaying());
   QCOMPARE( m_timerService->getLastRequestedTime(), -1);
}

void test_SequenceRunner::testPauseThenPlay()
{
   QSignalSpy spyActivateRow( m_runner, SIGNAL(activateRow(int)));

   SequenceItem item1( -1, QString("1. Track 01"), SequenceItem::TRACK );
   m_list << &item1;

   SequenceItem item2( 500, QString("2. Track 02"), SequenceItem::TRACK );
   m_list << &item2;

   m_runner->selectIndex(0);
   m_runner->play();
   QCOMPARE( spyActivateRow.count(), 1);
   QCOMPARE( spyActivateRow.at(0).at(0).toInt(), 0);  /* item 0 activated */

   m_runner->pause();
   m_runner->play();

   QCOMPARE( spyActivateRow.count(), 2);
   QCOMPARE( spyActivateRow.at(1).at(0).toInt(), 0);  /* still item 0 activated */
}

/**
 * @brief A 2 events sequence is generated within a function and
 *        the timeout expires when that function is over.
 *
 * MORAL: always allocate SequenceItem with a "new" and not in function scope
 */
void test_SequenceRunner::testObjectsScope()
{
   QSignalSpy spyActivate( m_runner, SIGNAL(activateLight(QString,bool)));

   itemsInAnotherScope();

   try
   {
      m_timerService->simulateTimeout();
   }
   catch(...)
   {
      QFAIL("Unexpected exception");
   }

   QCOMPARE( spyActivate.length(), 2);
}

/**
 * @brief create new items for the sequence in this function with
 *  new operator
 */
void test_SequenceRunner::itemsInAnotherScope()
{
   SequenceItem *item1 = new SequenceItem( -1, QString("Day Ambient"), SequenceItem::LIGHT,
                                           SequenceItem::NEXT_IS_UNDEFINED, this );
   SequenceItem *item2 = new SequenceItem( 500, QString("Night Ambient"), SequenceItem::LIGHT,
                                           SequenceItem::NEXT_IS_UNDEFINED, this );

   m_model->addSequenceItem( item1);
   m_model->addSequenceItem( item2);

   m_runner->selectIndex(0);
   m_runner->play();
}

/**
 * after launching second item, jump back to first, that is
 * the 'following' for second item.
 */
void test_SequenceRunner::testValidJump()
{
   SequenceItem item0( 1000, QString("1. Track 01"), SequenceItem::TRACK,
                       SequenceItem::NEXT_IS_UNDEFINED, this );
   m_list << &item0;
   SequenceItem item1( 500, QString("2. Track 02"), SequenceItem::TRACK, 0, this );
   m_list << &item1;

   m_runner->selectIndex(0);
   m_runner->play();

   QCOMPARE( m_timerService->getLastRequestedTime(), 500);
   /* this activates item1 */
   m_timerService->simulateTimeout();

   /* item0 has been re-armed */
   QCOMPARE( m_timerService->getLastRequestedTime(), 1000);
}

void test_SequenceRunner::testSelectItem()
{
   QSignalSpy spyActivate( m_runner, SIGNAL(activateMedia(QString,bool)) );

   SequenceItem item0( 1000, QString("1. Track 01"), SequenceItem::TRACK,
                       SequenceItem::NEXT_IS_UNDEFINED, this );
   m_list << &item0;
   SequenceItem item1( 500, QString("2. Track 02"), SequenceItem::TRACK,
                       SequenceItem::NEXT_IS_UNDEFINED, this );
   m_list << &item1;

   m_runner->selectIndex(0);
   m_runner->play();

   /* before timer expires, double click on item1 */
   m_runner->selectIndex( m_model->index(1));

   /* timer non running. Item1 fired immediately on 'play' */
   QCOMPARE( m_timerService->getLastRequestedTime(), -1);

   m_runner->play();
   QCOMPARE( spyActivate.length(), 2);
   QCOMPARE( spyActivate.at(1).at(0).toString(), QString("2. Track 02") );
}

void test_SequenceRunner::testSignalForActiveChange()
{
   QSignalSpy spyActivateRow( m_runner, SIGNAL(activateRow(int)));

   SequenceItem item0( 1000, QString("1. Track 01"), SequenceItem::TRACK,
                       SequenceItem::NEXT_IS_UNDEFINED, this );
   m_list << &item0;
   SequenceItem item1( 500, QString("2. Track 02"), SequenceItem::TRACK,
                       SequenceItem::NEXT_IS_UNDEFINED, this );
   m_list << &item1;

   m_runner->selectIndex(0);
   QCOMPARE( spyActivateRow.count(), 0);  /* item 0 selected by GUI. No signal emitted */

   m_runner->play();
   QCOMPARE( spyActivateRow.count(), 1);
   QCOMPARE( spyActivateRow.at(0).at(0).toInt(), 0);  /* item 0 activated */

   m_timerService->simulateTimeout();
   QCOMPARE( spyActivateRow.count(), 2);
   QCOMPARE( spyActivateRow.at(1).at(0).toInt(), 1);  /* item 1 activated */
}

void test_SequenceRunner::testSignalTimerChange()
{
   QSignalSpy spyTimeToNextEvent( m_runner, SIGNAL(timeToNextEvent(int)) );

   SequenceItem item1( -1, QString("1. Track 01"), SequenceItem::TRACK);
   m_model->addSequenceItem( &item1);
   SequenceItem item2( 500, QString("2. Track 02"), SequenceItem::TRACK);
   m_model->addSequenceItem( &item2);
   SequenceItem item3( 800, QString("3. Track 03"), SequenceItem::TRACK);
   m_model->addSequenceItem( &item3);

   m_runner->selectIndex(0);
   m_runner->play();

   QCOMPARE( spyTimeToNextEvent.count(), 1);
   QCOMPARE( spyTimeToNextEvent.at(0).at(0).value<int>(), 500);

   m_timerService->simulateTimeout();

   QCOMPARE( spyTimeToNextEvent.count(), 2);
   QCOMPARE( spyTimeToNextEvent.at(1).at(0).value<int>(), 800);
}

/**
 * select an item and play sequence; after play command, re-select same item
 * (before timer expiration). Sequence must not be paused.
 */
void test_SequenceRunner::testSelectAfterActivation()
{
   SequenceItem item1( -1, QString("1. Track 01"), SequenceItem::TRACK);
   m_list << &item1;
   SequenceItem item2( 5000, QString("2. Track 02"), SequenceItem::TRACK);
   m_list << &item2;

   m_runner->selectIndex(0);
   m_runner->play();

   m_runner->selectIndex(0);
   QVERIFY( m_runner->isPlaying());
}

void test_SequenceRunner::testPlayPauseSignals()
{
   QSignalSpy spyIsRunning( m_runner, SIGNAL(sequenceIsRunning(bool)) );

   SequenceItem item1( -1, QString("1. Track 01"), SequenceItem::TRACK);
   m_list << &item1;
   SequenceItem item2( 5000, QString("2. Track 02"), SequenceItem::TRACK);
   m_list << &item2;

   m_runner->selectIndex(0);
   QCOMPARE( spyIsRunning.count(), 1);
   QCOMPARE( spyIsRunning.at(0).at(0).toBool(), false);

   m_runner->play();

   QCOMPARE( spyIsRunning.count(), 2);
   QCOMPARE( spyIsRunning.at(1).at(0).toBool(), true);

   m_runner->pause();
   QCOMPARE( spyIsRunning.count(), 3);
   QCOMPARE( spyIsRunning.at(2).at(0).toBool(), false);
}

void test_SequenceRunner::testSequenceOver()
{
   QSignalSpy spyIsRunning( m_runner, SIGNAL(sequenceIsRunning(bool)) );

   SequenceItem item1( 500, QString("1. Track 01"), SequenceItem::TRACK);
   m_model->addSequenceItem( &item1);

   m_runner->selectIndex(0);
   m_runner->play();

   QCOMPARE( spyIsRunning.count(), 2);
   QCOMPARE( spyIsRunning.at(1).at(0).toBool(), true);

   QCOMPARE( m_timerService->getLastRequestedTime(), -1);
}

/**
 * play sequence before adding any item
 */
void test_SequenceRunner::testEmptySequence()
{
   QSignalSpy spySequenceOver( m_runner, SIGNAL(sequenceIsOver()) );

   m_runner->play();

   QCOMPARE(spySequenceOver.count(), 1);
}

/**
 * play an invalid entry.
 * An error is rised but sequence is running.
 */
void test_SequenceRunner::testPlayUninitializedEntry()
{
   QSignalSpy sequenceRunningSpy( m_runner, SIGNAL(sequenceIsRunning(bool)) );
   QSignalSpy sequenceErrorSpy( m_runner, SIGNAL(sequenceError(QString)) );

   m_model->createEmptyItem();
   m_runner->selectIndex(0);
   m_runner->play();

   QCOMPARE( sequenceRunningSpy.length(), 2);
   QCOMPARE( sequenceRunningSpy.at(1).at(0).toBool(), true);

   QCOMPARE( sequenceErrorSpy.length(), 1);
   QCOMPARE( sequenceErrorSpy.at(0).at(0).toString(), QString("Invalid event type") );
}

